import { createFilter } from '@rollup/pluginutils'
import { stat } from 'fs/promises'
import { omit } from 'lodash-es'
import path from 'path'
import { CWD } from './constants'
import { resolveEmccConfig } from './helpers/emscripten'
import { defaultInstanceFactory } from './helpers/webAssembly'
import { CustomWebAssemblyInstanceFactory, UserOptions } from './types'
import { hashString, resolveModuleId } from './utils'

export const resolveAscOptions = (
  ascOptions: UserOptions['asc'] = {},
  options: UserOptions = {}
) => {
  let {
    include = ['**/*.as'],
    exclude,
    rename = (id) => `${path.parse(id).name}-${hashString(id, 'hex', 8)}.wasm`,
    dir = './node_modules/.rollup-plugin-wasm/asc',
    binaryDir,
    textDir,
    jsDir,
    idlDir,
    tsdDir,
    ...compilerOptions
  } = ascOptions
  let { debug = false } = options

  const filter = createFilter(include, exclude, { resolve: CWD })
  compilerOptions = omit(compilerOptions, [
    'version',
    'help',
    'noColors',
    'target',
    'outFile',
    'binaryFile',
    'textFile',
    'sourceMap',
    'debug',
    'noEmit',
    'listFiles',
  ])

  const asBind = resolveModuleId('as-bind')

  const compilerArgv = [
    debug ? ['--debug', '--target', 'debug'] : ['--target', 'release'],
    asBind && ['--exportRuntime', `--transform=${asBind}`],
    ...Object.entries(compilerOptions).map(([opt, val]) => {
      if (val === true) {
        return `--${opt}`
      } else if (!val) {
        return
      } else if (Array.isArray(val)) {
        return val.map((val) => `--${opt}=${val}`)
      }
      return `--${opt}=${val}`
    }),
  ]
    .flat()
    .filter(Boolean) as string[]

  dir = path.resolve(CWD, dir)
  binaryDir = path.join(dir, binaryDir || '')
  textDir = textDir != null ? path.join(dir, textDir) : undefined
  jsDir = jsDir != null ? path.join(dir, jsDir) : undefined
  idlDir = idlDir != null ? path.join(dir, idlDir) : undefined
  tsdDir = tsdDir != null ? path.join(dir, tsdDir) : undefined

  return {
    debug,
    filter,
    enableAsBind: !!asBind,
    compilerArgv,
    rename,
    dir,
    binaryDir,
    textDir,
    jsDir,
    idlDir,
    tsdDir,
  }
}

const resolveEmccOptions = (emccOptions: UserOptions['emscripten'] = {}, options: UserOptions = {}) => {
  let { modules = [], ...emccConfig } = emccOptions
  let { debug = false } = options

  return {
    modules,
    argv: [...(debug ? ['-g', '-gsource-map'] : ['-O3']), ...resolveEmccConfig(emccConfig)],
  }
}

const resolveGolangOptions = (
  emccOptions: UserOptions['golang'] = {},
  options: UserOptions = {}
) => {
  let { goArgv = [], tinygo = false, tinygoArgv = [], include = ['**/*.go'], exclude } = emccOptions
  let { debug = false } = options
  const filter = createFilter(include, exclude, { resolve: CWD })

  goArgv = ['build', ...goArgv].filter((v) => v && typeof v === 'string') as string[]
  tinygoArgv = ['build', ...tinygoArgv, '-target', 'wasm', !debug && '-no-debug'].filter(
    (v) => v && typeof v === 'string'
  ) as string[]
  return {
    goArgv,
    tinygo,
    tinygoArgv,
    debug,
    filter,
  }
}

export const resolveOptions = (options: UserOptions = {}) => {
  const {
    asc,
    emscripten,
    golang,
    inline: _inline = false,
    useInstanceFactory: _useInstanceFactory,
    target,
  } = options

  const inline: (fileName: string, id: string) => boolean | Promise<boolean> =
    typeof _inline === 'number'
      ? (fileName) =>
          stat(fileName).then(
            (stat) => stat.size < _inline * 1024,
            () => false
          )
      : typeof _inline === 'function'
      ? _inline
      : () => !!_inline

  let useInstanceFactory: undefined | CustomWebAssemblyInstanceFactory
  if (_useInstanceFactory) {
    if (typeof _useInstanceFactory === 'function') {
      useInstanceFactory = _useInstanceFactory
    } else {
      useInstanceFactory = defaultInstanceFactory
    }
  }

  return {
    asc: resolveAscOptions(asc, options),
    emscripten: resolveEmccOptions(emscripten, options),
    golang: resolveGolangOptions(golang, options),
    inline,
    useInstanceFactory,
    target,
  }
}

export type ResolvedOptions = ReturnType<typeof resolveOptions>
